/*
    This file is part of QTau
    Copyright (C) 2013-2018  Tobias "Tomoko" Platen <tplaten@posteo.de>
    Copyright (C) 2013       digited       <https://github.com/digited>
    Copyright (C) 2010-2013  HAL@ShurabaP  <https://github.com/haruneko>

    QTau is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    SPDX-License-Identifier: GPL-3.0+
*/
#define __devloglevel__ 4

#include "jackaudio.h"
#include <assert.h>

#include <stdio.h>
#include <QDebug>
#include <QIcon>
#include <QMessageBox>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/eventfd.h>
#include <unistd.h>

#include <jack/midiport.h>
#include <iostream>

int JackAudio::createEFD() {
  _eventfd = eventfd(0, 0);
  return _eventfd;
}

int JackAudio::readMidiData(char *buffer, int maxlength) {
  int count = jack_ringbuffer_read_space(this->_midi_rb);
  if (count > 0) {
    if (count > maxlength) count = maxlength;
    jack_ringbuffer_read(this->_midi_rb, buffer, count);
  }
  return count;
}

int JackAudio::process(jack_nframes_t nframes, void *arg) {
  JackAudio *conn = (JackAudio *)arg;

  // midi input handing
  void *port_buf = jack_port_get_buffer(conn->_midi_port, nframes);
  jack_nframes_t event_count = jack_midi_get_event_count(port_buf);
  if (event_count > 0) {
    for (jack_nframes_t i = 0; i < event_count; i++) {
      jack_midi_event_t in_event;
      // jack_position_t position;
      jack_midi_event_get(&in_event, port_buf, i);
      // FIXME: sysex support
      jack_ringbuffer_write(conn->_midi_rb, (char *)in_event.buffer,
                            in_event.size);
    }
  }

  // audio output
  sample_t *write_samp =
      (sample_t *)jack_port_get_buffer(conn->_write_port, nframes);
  if (jack_ringbuffer_read_space(conn->_write_rb) >=
      sizeof(sample_t) * nframes) {
    jack_ringbuffer_read(conn->_write_rb, (char *)write_samp,
                         sizeof(sample_t) * nframes);
  } else {
    for (unsigned int i = 0; i < nframes; i++)
      write_samp[i] = 0.0;  // buffer underrun
    DEVLOG_DEBUG("xrun in JackAudio::process");
  }

  // transport control
  if (conn->_use_transport) {
    conn->_transport_state = jack_transport_query(conn->_client, NULL);

    if (conn->_transport_state == JackTransportStopped &&
        conn->_previous_transport_state == JackTransportRolling) {
      conn->_state_changed = true;
    }

    if (conn->_transport_state == JackTransportStarting &&
        conn->_previous_transport_state != JackTransportStarting) {
      conn->_state_changed = true;
    }

    if (conn->_transport_state == JackTransportRolling &&
        conn->_previous_transport_state != JackTransportRolling) {
      conn->_state_changed = true;
    }

    conn->_previous_transport_state = conn->_transport_state;
  }
  if (conn->_transport_command == TRANSPORT_START)
    jack_transport_start(conn->_client);
  if (conn->_transport_command == TRANSPORT_STOP)
    jack_transport_stop(conn->_client);
  if (conn->_transport_command == TRANSPORT_ZERO) {
    jack_position_t pos;
    pos.valid = (jack_position_bits_t)0;
    pos.frame = 0;
    jack_transport_stop(conn->_client);
    jack_transport_reposition(conn->_client, &pos);
  }
  if (conn->_transport_command == TRANSPORT_STARTPOS) {
    jack_position_t pos;
    pos.valid = (jack_position_bits_t)0;
    pos.frame = conn->_startpos * conn->sampleRate();
    jack_transport_stop(conn->_client);
    jack_transport_reposition(conn->_client, &pos);
    jack_transport_start(conn->_client);
  }
  conn->_transport_command = 0;

  // notify
  uint64_t u = 1;
  if (write(conn->_eventfd, &u, sizeof(uint64_t)) != 8) return 1;

  // no error
  return 0;
}

int JackAudio::sync(jack_transport_state_t state, jack_position_t *pos,
                    void *arg) {
  (void)state;

  // start tranport
  JackAudio *conn = (JackAudio *)arg;
  if (conn->_use_transport) {
    conn->_position = pos->frame;
    return 1;
  } else
    return 1;
}

JackAudio::JackAudio(bool autoconnect) {
  _client = jack_client_open("QTau", JackNoStartServer, NULL);

  if (_client == NULL) {
    QMessageBox msgbox;
    msgbox.setText("jack is not running");
    msgbox.exec();
    exit(1);
  }

  _buffer_size = jack_get_buffer_size(_client);

  // TODO configurable port names ???
  _write_port = jack_port_register(_client, "out", JACK_DEFAULT_AUDIO_TYPE,
                                   JackPortIsOutput, _buffer_size);
  _write_rb = jack_ringbuffer_create(sizeof(sample_t) * 4096);

  _midi_rb = jack_ringbuffer_create(4096);
  _midi_port = jack_port_register(_client, "midi_in", JACK_DEFAULT_MIDI_TYPE,
                                  JackPortIsInput, 0);

  jack_set_process_callback(_client, process, this);
  jack_set_sync_callback(_client, sync, this);

  jack_activate(_client);

  if (autoconnect) {
    jack_connect(_client, "QTau:out", "system:playback_2");  // FOR testing
    jack_connect(_client, "QTau:out", "system:playback_1");  // FOR testing
  }

  _transport_command = TRANSPORT_ZERO;
  _use_transport = true;
  _playback = false;
}

int JackAudio::sampleRate() { return jack_get_sample_rate(_client); }

int JackAudio::writeData(void *framebuf, int bytes_per_frame) {
  if (jack_ringbuffer_write_space(_write_rb) >= (unsigned int)bytes_per_frame) {
    return jack_ringbuffer_write(_write_rb, (char *)framebuf, bytes_per_frame);
  }
  DEVLOG_DEBUG("xrun in JackAudio::writeData");  // FIXME do not log while
                                                 // program startup
  return 0;  // not data was written
}

float *JackAudio::allocateBuffer() {
  if (_buffer_size == 0) return NULL;
  return new float[_buffer_size];
}

void JackAudio::shutdown() {
  jack_deactivate(_client);
  jack_client_close(_client);
}
